package org.poker;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;

import org.poker.exception.HandException;
import org.poker.model.Card;
import org.poker.model.Suit;

public class ChecksTheHand {



    /**
     * verifica se existe uma sequencia alta.
     *
     * @param cartasAux
     * @return
     */
    public static boolean hightSequence(List<Card> cartas) {
	List<Card> cartasAux = new ArrayList<Card>(cartas);
	Collections.<Card>sort(cartasAux, new Card());
	return sequence(cartasAux) && cartasAux.get(Poker.Hand.CARD_1).getNumber() == Card.CARD_A && cartasAux.get(Poker.Hand.CARD_5).getNumber() == Card.CARD_K;
    }

    /**
     * Se existe alguma carta, por numero, de cards1 em cards2
     * 	 retorna true
     *
     * @param cards1
     * @param cards2
     * @return
     */
    public static boolean contain(List<Card> cards1, List<Card> cards2) {
	for (Card card1 : cards1)
	    for (Card card2 : cards2)
		if(card1.getNumber()==card2.getNumber())
		    return true;
	return false;
    }

    /**
     * retorna true caso todos os naipes sejam iguais
     *
     * @param cartas
     * @return
     */
    public static boolean naipesIguais(List<Card> cartas) {
	return maxCardsByGroup(cartas, Suit.class) == 5;
    }

    /**
     * retorna true caso exita um par real
     *
     * @param cartas
     * @return
     */
    public static boolean parMaior(List<Card> cartas) {
	Map<Integer,Integer> map = toMap(cartas);
	for(int numero : new int[]{Card.CARD_A, Card.CARD_J, Card.CARD_Q, Card.CARD_K}  )
	    if(map.containsKey(numero) && map.get(numero)>=2)
		return true;
	return false;
    }

    /**
     * retorna true caso exita um par
     *
     * @param cartas
     * @return
     */
    public static boolean par(List<Card> cartas) {
	Map<Integer,Integer> map = toMap(cartas);
	for(Card card:cartas)
	    if(map.containsKey(card.getNumber()) && map.get(card.getNumber())==2)
		return true;
	return false;
    }

    /**
     * retorna true caso exita sequencia
     *
     * @param cartasAux
     * @return
     */
    public static boolean sequence(List<Card> cartas) {

	List<Card> cartasAux = new ArrayList<Card>(cartas);
	Collections.<Card>sort(cartasAux, new Card());
	int numeroAnterior = cartasAux.get(Poker.Hand.CARD_1).getNumber();
	for(Card carta : cartasAux) {
	    if(numeroAnterior == 1 && carta.getNumber() == 10 && cartasAux.indexOf(carta) != Poker.Hand.CARD_1)
		numeroAnterior = 9;
	    if (numeroAnterior != carta.getNumber()-1 && cartasAux.indexOf(carta) != Poker.Hand.CARD_1)
		return false;
	    numeroAnterior = carta.getNumber();
	}
	return true;
    }

    /**
     * retorna true caso exita sequencia, e qtd de intervalos na sequencia.
     *
     *	para formar a sequencia, preencho com o numero de intervalos.
     *
     *	ex.:	1 intervalo
     *     	1, 2, 3, 4, x
     *          1, 2, 3, x, 5
     *	        1, 2, x, 4, 5
     *	        1, x, 3, 4, 5
     *	        x, 2, 3, 4, 5
     *
     *    	2 intervalo
     *	        x, 2, x, 4, 5
     *
     *        	3 intervalo
     *	        x, 2, x, 4, x
     *
     * @param cartas
     * @return
     */
    public static boolean sequence(List<Card> cartas, Integer qtdIntervalos) throws HandException{

	List<Card> cartasAux = new ArrayList<Card>(cartas);
	Collections.<Card>sort(cartasAux, new Card());
	PriorityQueue<Card> priorityQueueCards = new PriorityQueue<Card>(cartas);

	Card cartaAnterior = priorityQueueCards.poll();
	Card carta = priorityQueueCards.poll();

	int qtdIntervalosInt = qtdIntervalos == null?Integer.valueOf(0).intValue():qtdIntervalos.intValue();
	int intervalosUtilizados = 0;
	int adicional = 1;
	while(true) {
	    if(carta.getNumber() == cartaAnterior.getNumber() + adicional) {
		if(priorityQueueCards.isEmpty() && intervalosUtilizados <= qtdIntervalosInt)
		    return true;
		cartaAnterior = carta;
		carta = priorityQueueCards.poll();
		adicional = 1;
	    } else if(qtdIntervalosInt > intervalosUtilizados) {
		intervalosUtilizados++;
		adicional++;
	    } else {
		if(cartasAux.indexOf(new Card(Card.CARD_A, Suit.COPAS))>=0 ||
			cartasAux.indexOf(new Card(Card.CARD_A, Suit.ESPADAS))>=0 ||
			cartasAux.indexOf(new Card(Card.CARD_A, Suit.OUROS))>=0 ||
			cartasAux.indexOf(new Card(Card.CARD_A, Suit.PAUS))>=0) {
		    switchCardA(cartasAux);
		    return sequence(cartasAux, qtdIntervalos);
		}
		return false;
	    }
	}
    }

    /**
     * retorna a qtd maxima de cartas com numeros iguais por grupo
     * ex.: [ A C ,  A P ,  J O ,  A E ,  5 O ]
     *       max 3 carta por grupo
     *
     * ex.: [ A C ,  A P ,  J O ,  J E ,  5 O ]
     *       max 2 carta por grupo
     *
     * @param cartas
     * @return
     */
    public static int maxCardsByGroup(List<Card> cartas, Class<?> type) {
	Map<Integer,Integer> map = null;
	int frequenciaMaior = 0;
	if(type == Card.class)
	    map = toMap(cartas);
	else if(type == Suit.class)
	    map = toMap(toNaipeList(cartas));
	for(int frequencia : map.values())
	    if(frequencia > frequenciaMaior)
		frequenciaMaior = frequencia;
	return frequenciaMaior;
    }

    /**
     * retorna a qtd de grupos
     * ex.: [ A C ,  A P ,  J O ,  A E ,  5 O ]
     *       tem 3 grupos
     *
     * @param cartas
     * @return
     */
    public static int cardGroups(Collection<Card> cartas) {
	return toMap(cartas).keySet().size();
    }

    private static void switchCardA(List<Card> cards) throws HandException {
	if(cards.size()==0)
	    throw new HandException(">> cards size == 0");
	Card cardA = cards.remove(0);
	if(!cardA.equals(new Card(Card.CARD_A, Suit.COPAS)) &&
		!cardA.equals(new Card(Card.CARD_A, Suit.ESPADAS)) &&
		!cardA.equals(new Card(Card.CARD_A, Suit.OUROS)) &&
		!cardA.equals(new Card(Card.CARD_A, Suit.PAUS))  )
	    throw new HandException(">> nao existe card A em swithCardA");
	cards.add(new Card(Card.CARD_AA, cardA.getNaipe()));
    }

    /**
     * jogo para um map, apenas o valor (numero ou naipe)
     * utilizando como chave, o valor da carta ou do naipe,
     * e somando sempre +1
     * no value a cada carta de mesma chave
     *
     *
     * @param collection
     * @return
     */
    private static Map<Integer,Integer> toMap(Collection<?> collection) {
	Map<Integer,Integer> resultMap = new HashMap<Integer,Integer>();
	for (Object obj : collection) {
	    if(obj instanceof Card) {
		if(!resultMap.containsKey(((Card)obj).getNumber()))
		    resultMap.put(((Card)obj).getNumber(), 1);
		else
		    resultMap.put(((Card)obj).getNumber(), resultMap.get(((Card)obj).getNumber())+1);
	    } else if (obj instanceof Suit) {
		if(!resultMap.containsKey(((Suit)obj).getNaipe()))
		    resultMap.put(((Suit)obj).getNaipe(), 1);
		else
		    resultMap.put(((Suit)obj).getNaipe(), resultMap.get(((Suit)obj).getNaipe())+1);
	    }
	}
	return resultMap;
    }

    private static List<Suit> toNaipeList(List<Card> cartas) {
	List<Suit> result = new ArrayList<Suit>();
	for(Card carta : cartas) {
	    Suit naipe = new Suit(carta.getNaipe());
	    result.add(naipe);
	}
	return result;
    }

}
